package gbench.sandbox.tuple;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import static gbench.sandbox.tuple.JunitM6.Data.*;
import static gbench.sandbox.tuple.JunitM6.Elem.*;

public class JunitM6 {
    
    /**
     * @return
     */
    public static Stream<Integer> stm() {
        return Stream.iterate(0, i -> i + 1);
    }

    /**
     * @param maxSize
     * @return 0,1,2,3,4,...
     */
    public static Stream<Integer> stm(final Number maxSize) {
        return stm().limit(maxSize.longValue());
    }

    /**
     * @param maxSize
     * @return 0,1,2,3,4,...
     */
    public static Stream<Integer> stm(final Number maxSize,final Predicate<Integer> predicate) {
        return stm().limit(maxSize.longValue()).filter(predicate);
    }
    
    /**
     * @param <T>
     * @param <U>
     */
    public static class Tuple2<T, U> {

        /**
         * @param t
         * @param u
         */
        public Tuple2(final T t, final U u) {
            _1 = t;
            _2 = u;
        }
        
        /**
         * 
         * @param n 位置序号
         * @param v 数据值
         * @return 复制品
         */
        @SuppressWarnings("unchecked")
        public Tuple2<T,U> set(int n,Object v) {
            return n==1?new Tuple2<>((T)v,_2):new Tuple2<>(_1,(U)v);
        }

        /**
         * 
         * @param n 位置序号
         * @param v 数据值
         * @return 对象本身
         */
        public Tuple2<T,U> set2(int n,Object v) {
            try {
                final var field = Tuple2.class.getField(MessageFormat.format("_{0}", n));
                field.setAccessible(true);
                field.set(this, v);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return this;
        }
        
        /**
         * 字符串格式化
         */
        public String toString() {
            final var line = MessageFormat.format("[{0} --> {1}]",this._1,this._2);
            return line;
        }
        
        /**
         * 对象克隆
         */
        public Tuple2<T,U> clone(){
            return new Tuple2<T,U>(this._1,this._2);
        }

        public final T _1; // 1号位置元素
        public final U _2; // 2号位置元素

        /**
         * @param tt
         * @param uu
         * @param <T>
         * @param <U>
         * @return
         */
        public static <T, U> Stream<Tuple2<T, U>> zip(final T[] tt, final U[] uu) {
            return stm(Math.max(tt.length, uu.length)).map(i -> new Tuple2<>(tt[i % tt.length], uu[i % uu.length]));
        }
    }

    /**
     * @param <T> 数据类型
     */
    public static class Elem<T> extends Tuple2<T, Elem<T>> {

        /**
         * @param t
         * @param parent
         */
        public Elem(final T t, final Elem<T> parent) {
            super(t, parent);
        }

        /**
         * @param t
         */
        public Elem(final T t) {
            super(t, null);
        }

        /**
         * @param elem
         * @param <U>
         * @return
         */
        public static <U> List<U> data(Elem<U> elem) {
            return elem._2 == null
                    ? Stream.of(elem._1).collect(Collectors.toList())
                    : Stream.of(data(elem._2), Arrays.asList(elem._1)).flatMap(e -> e.stream()).collect(Collectors.toList());
        }

        /**
         * @return
         */
        public List<T> data() {
            return data(this);
        }
        
        /**
         * 
         * @param t
         * @return
         */
        public Elem<T> cons(T t){
            return E(t,this);
        }
        
        /**
         * @param t
         */
        public static <T> Elem<T> E(final T t) {
            return E(t,null);
        }
        
        /**
         * @param t
         */
        public static <T> Elem<T> E(final T t,Elem<T> elem) {
            return new Elem<>(t, elem);
        }
        
        /**
         * 
         * @param <T>
         * @param e
         * @return
         */
        public static <T> int length(Elem<T> e) {
            return (e==null)? 0 : 1+length(e._2);
        }
        
        /**
         * fmap
         * @param <T>
         * @param e
         * @return
         */
        public static <T,U> Elem<U> fmap(Function<T,U> mapper,Elem<T> e) {
            return e==null?null:E(mapper.apply(e._1),fmap(mapper,e._2));
        }
        
        /**
         * 函子
         * @param <T>
         * @param e1
         * @param e2
         * @return
         */
        public static <T> Elem<T> set2(Elem<T> elem,Function<T,Elem<T>>bind){
            try {
                final var fld = Elem.class.getField("_2");
                fld.setAccessible(true);
                fld.set(elem, bind.apply(elem._1));
            }catch(Exception e) {
                e.printStackTrace();
            }
            
            return elem;
        }
    }
    
    /**
     * 
     * @author gbench
     *
     * @param <K>
     * @param <V>
     */
    public static class Data extends Tuple2<String,List<Tuple2<String,Tuple2<Object,Object>>>>{

        public Data(String t, List<Tuple2<String,Tuple2<Object,Object>>> u) {
            super(t, u);
        }
        
        public Data(String t) {
            super(t,new LinkedList<>());
        }
        
        /**
         * 构建结构
         * @param line 数据字符串
         * @return 数据对象
         */
        public static Data DATA(final String line) {
            final var ss = line.split("[,\\s]+");
            final var construct = new Data(ss[0]);
            
            for(int i=1;i<ss.length;i++) {
                final var tt = ss[i].split("[:]+");
                String field = tt.length<2?MessageFormat.format("_{0}",i):tt[0];
                String clsName = tt.length>1?tt[1]:tt[0];
                Object cls = Object.class;
                try {
                    switch (clsName) {
                    
                    case "Elem":{
                        cls = gbench.sandbox.tuple.JunitM6.Elem.class;
                        break;
                    }
                    default:
                            cls = Class.forName(MessageFormat.format("java.lang.{0}",clsName));
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                
                construct._2.add(new Tuple2<>(field,new Tuple2<>(cls,null)));
            }
            return construct;
        }
        
        /**
         * 列表队形
         */
        public static Data LIST = DATA("List header::Elem");
        
        /**
         * 
         */
        public Data clone() {
            final var construct = new Data(this._1);
            this._2.forEach(field->{
                final var _fld = new Tuple2<String,Tuple2<Object,Object>>(field._1,
                    new Tuple2<>(field._2._1,field._2._2));
                construct._2.add(_fld);
            });
            return construct;
        }
        
        /**
         * 创建值对象
         * @param objects
         * @return
         */
        public Data create(Object ...objects){
            final var ai = new AtomicInteger(0);
            final var vv = this.clone();
            vv._2.forEach(field->{
                try {
                    final var _2fld = Tuple2.class.getField("_2");
                    _2fld.setAccessible(true);
                    final var v = objects[ai.getAndIncrement()%objects.length];
                    final var type = field._2._1;
                    if(type instanceof Class<?>)if(v!=null && ((Class<?>)type).isAssignableFrom(v.getClass())) {
                        _2fld.set(field._2,v );
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } 
            });
            return vv;
        }
        
        /**
         * 
         * @return
         */
        public Map<String,Object> values(){
            final var values = new HashMap<String,Object>();
            this._2.forEach(field->{
                values.put(field._1, field._2._2);
            });
            return values;
        }
        
        /**
         * 类型
         * @return
         */
        public Map<String,Object> types(){
            final var values = new HashMap<String,Object>();
            this._2.forEach(field->{
                values.put(field._1, field._2._1);
            });
            return values;
        }
        
        /**
         * 
         * @param <T>
         * @param clazz
         * @return
         */
        @SuppressWarnings("unchecked")
        public <T> T get(final String name,T t) {
            final var v = this.values().get(name);
           return (T) v;
        }
        
        /**
         * 
         * @param <T>
         * @param clazz
         * @return
         */
        @SuppressWarnings("unchecked")
        public <T> Elem<T> list(final String name,T t) {
            final var v = this.values().get(name);
           return (Elem<T>) v;
        }
        
        /**
         * 
         * @param <T>
         * @param clazz
         * @return
         */
        @SuppressWarnings("unchecked")
        public <T> Elem<T> list(T t) {
            final var v = this.values().get("header");
           return (Elem<T>) v;
        }
        
        /**
         * 
         * @param <T>
         * @param n
         * @param e
         * @return
         */
        public static <T> Elem<T> take(final int n,final Elem<T>e){
            return e!=null?E(e._1,n<1?null:take(n-1,e._2)):null;
        };
    }
    
    @Test
    public void foo() {
        final var User = DATA("User name::String sex::String Double");
        System.out.println(User);
        System.out.println(User.create("zhansan","man",456.0));
        
        final var Addr = DATA("Address city::String district::String");
        final var addr1 = Addr.create("北京","东城区");
        final var addr2 = Addr.create("上海","徐汇区");
        Stream.of(addr1,addr2).forEach(addr->{
            System.out.println(addr);
        });
        
        final var types = Addr.create("上海","徐汇区").types();
        System.out.println(types);

        final var e = LIST.create(E(1)).list(0).cons(2).cons(3).cons(4);
        System.out.println(e);
        System.out.println(length(e));
        System.out.println(fmap(p->p+1,e));
        final var p = E(1);
        final var _e = set2(p,x->p);
        System.out.println(take(1000,_e));
    }
    
    @Test
    public void bar() {
       final var dd =  DATA("Either left::Integer right::Integer");
       final var x = dd.create(1,2);
       System.out.println(x);
    }
    
    public static Map<String,String> M(String ...ss){
        final var map = new HashMap<String,String>();
        for(int i=0;i<ss.length;i+=2) {
            map.put(ss[i],i+1<ss.length?ss[i+1]:null);
        }
        return map;
    }
    
    public String parse(String line) {
        final var cc = line.split("");//字符分解器
        final var stack = new Stack<String>();
        final var terms = new HashMap<String,String>();
        final var atom = new AtomicInteger(0);
        final Supplier<String> key= ()->MessageFormat.format("IMT{0}",atom.getAndIncrement());// 中间项目
        final var brackets = M(")","(","]","[","}","{");// 括号
        
        for(var c:cc) {
            if(brackets.containsKey(c)) {// 出现字符项目
                final var buffer = new LinkedList<String>();// 回溯缓存
                while(!stack.empty()) {
                    final var w = stack.pop();
                    if(brackets.get(c).equals(w)) {
                        final var term_key = key.get();
                        Collections.reverse(buffer);// 倒序
                        terms.put(term_key, buffer.stream().collect(Collectors.joining()));
                        stack.push(term_key);
                        break;
                    }else {
                        buffer.add(w);
                    }//if
                }
            }else {
                stack.push(c);
            }
        }
        terms.put(key.get(), stack.stream().collect(Collectors.joining("")));
        
        terms.forEach((k,term)->{
            System.out.println(MessageFormat.format("{0} --> {1}", k,term));
        });
        
        return null;
    }
    
    @Test
    public void qux() {
        parse("1+2*{5*9+(34+5)*(89+9)}");
    }
    
    @Test
    @SuppressWarnings("unchecked")
    public void qux1() {
        String line = //"5+9*M0+M1**100/0.58";
                "a+b+c*d+e";
        
        @SuppressWarnings("unused")
        final var priorities = M("+","l0","*","l1","-","l0","/","l1","**","l1");
        final var pattern = MessageFormat.format("{0}", Stream.of("\\*{1,2}","\\+","-","/")
            .map(e->"("+e+")").collect(Collectors.joining("|")));
        final var terms = line.split(pattern);// 提出分词项目
        final var indexed_terms = Tuple2.zip(terms, stm(terms.length).toArray(Integer[]::new));// 分词项目
        
        final var matcher = Pattern.compile(pattern).matcher(line);
        final var operators = new LinkedList<String>();// 运算符
        while(matcher.find()) {
            final var wrd = matcher.group();
            operators.add(wrd);
        }
        
        final Tuple2<String,Integer>[] term_array = indexed_terms.toArray(Tuple2[]::new);
        final var expressions = new ArrayList<Tuple2<String,Tuple2<String,Object>>>(10);// expressions
        for(int i=1;i<term_array.length;i++) {
            final var op = operators.get(i-1);// 运算符号
            final var left = term_array[i-1]._1;
            final var right = term_array[i]._1;
            expressions.add(new Tuple2<>(op,new Tuple2<>(left,right)));
        }
        
        expressions.forEach(item->{
            System.out.println(item);
        });
        System.out.println();
    }
    
    public void qux2() {
        
    }

}
